#include "renderer_opengl.h"
#include "render_target_opengl.h"
#include "string_util.h"
#include "render_control.h"
#include "widget_factory.h"
#include "render_control_opengl_impl_base.h"
#include <list>

using namespace krryn::view;
using namespace krryn::system_imp;
using namespace krryn::math;

namespace{
	static class register_opengl_render_control_impl{
	public:
		register_opengl_render_control_impl(){
			krryn::gui::render_control_impl_manager::instance().register_control_imp(
				"opengl", krryn::gui::create_widget_base<krryn::gui::render_control_opengl_impl_base>());
		}
	} m_Register;
}


renderer_opengl::renderer_opengl(){
	glGetIntegerv(GL_MAX_TEXTURE_UNITS, &m_MaxTextureUnits);
	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &m_MaxBuffers);
	m_MatrixStacks.resize(view::matrix_type_count);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	// determine supported extensions and load them
	std::vector<std::string> m_Extensions = krryn::core::explode((char*)glGetString(GL_EXTENSIONS), " ");

    std::vector<std::string> l_Failed 
		= opengl_helper::load_extensions(m_Extensions, true);

	std::cout << "Max color attachments: " << m_MaxBuffers << std::endl;
}

void renderer_opengl::clear_buffers(int a_Buffers, color3 a_Color, float a_Depth, unsigned short a_Stencil){
	GLbitfield l_Mask = 0;

	if(a_Buffers & view::color_buffer){
		glClearColor(a_Color.m_R, a_Color.m_G, a_Color.m_B, 0);

		l_Mask |= GL_COLOR_BUFFER_BIT;
	}

	if(a_Buffers & view::depth_buffer){
		glClearDepth(a_Depth);

		l_Mask |= GL_DEPTH_BUFFER_BIT;
	}

	if(a_Buffers & view::stencil_buffer){
		glClearStencil(a_Stencil);

		l_Mask |= GL_STENCIL_BUFFER_BIT;
	}

	glClear(l_Mask);
}

krryn::core::refc_ptr<texture_catalog> renderer_opengl::get_texture_catalog(){
	static krryn::core::refc_ptr<view::texture_catalog> l_Catalog;
	if(!l_Catalog.get())
		l_Catalog = krryn::core::refc_ptr<view::texture_catalog>(
			new krryn::system_imp::texture_catalog_opengl());
	return l_Catalog;
}

krryn::core::refc_ptr<shader_catalog> renderer_opengl::get_shader_catalog(){
	static krryn::core::refc_ptr<view::shader_catalog> l_Catalog;
	if(!l_Catalog.get())
		l_Catalog = krryn::core::refc_ptr<view::shader_catalog>(
			new krryn::system_imp::shader_catalog_cg_opengl(*this));
	return l_Catalog;
}

krryn::core::refc_ptr<render_target> renderer_opengl::create_render_target(int a_Width, int a_Height){
	return krryn::core::refc_ptr<render_target>(
		new render_target_opengl(a_Width, a_Height));
}

void renderer_opengl::set_texture(unsigned int a_Unit, bool a_Enabled, krryn::core::refc_ptr<texture> a_Texture){

}

matrix_stack &renderer_opengl::get_matrix_stack(matrix_type a_Type){
	return m_MatrixStacks[a_Type];
}

void renderer_opengl::render(render_operation &a_Operation){
	std::list<GLenum> l_ClientStates;
	typedef std::list<GLenum>::iterator iterator;

	glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(m_MatrixStacks[view::matrix_projection].top().cell);
	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf(m_MatrixStacks[view::matrix_modelview].top().cell);

	for(size_t i = 0; i < a_Operation.m_Format.size(); i++){
		const view::vertex_element &l_Element = a_Operation.m_Format.at(i);

		// implicit cast should cause warning when data-types are incompatible
		GLsizei l_SizeInBytes = a_Operation.m_VertexBuffer->get_vertex_size_in_bytes();

		vertex_buffer_opengl *l_VertexBuffer = static_cast<vertex_buffer_opengl*>(
			a_Operation.m_VertexBuffer.get());

		glBindBufferARB(GL_ARRAY_BUFFER_ARB, l_VertexBuffer->get_buffer_id());
		
		const GLvoid *l_Pointer = map_to_vbo_offset(l_Element.m_OffsetInBytes);

		switch(l_Element.m_Semantic){
			case semantic_position:
				glVertexPointer(
					get_element_size(l_Element.m_Element),
					get_element_type(l_Element.m_Element),
					l_SizeInBytes,
					l_Pointer);

				l_ClientStates.push_back(GL_VERTEX_ARRAY);
				break;
			case semantic_normal:
				glNormalPointer(
					get_element_type(l_Element.m_Element),
					l_SizeInBytes,
					l_Pointer);
				l_ClientStates.push_back(GL_NORMAL_ARRAY);
				break;
			case semantic_diffuse:
				glColorPointer(
					get_element_size(l_Element.m_Element),
					get_element_type(l_Element.m_Element),
					l_SizeInBytes,
					l_Pointer);
				l_ClientStates.push_back(GL_COLOR_ARRAY);
				break;
			case semantic_specular:
				throw std::runtime_error("Invalid vertex binding semantic");
				break;
			case semantic_texture_coordinates:
				glClientActiveTextureARB(GL_TEXTURE0 + l_Element.m_Index);
				glTexCoordPointer(
					get_element_size(l_Element.m_Element),
					get_element_type(l_Element.m_Element),
					l_SizeInBytes,
					l_Pointer);
				l_ClientStates.push_back(GL_TEXTURE_COORD_ARRAY);
				break;
			case semantic_bitangent:
			case semantic_tangent:
			default:
				throw std::runtime_error("Invalid vertex binding semantic");
				// glVertexAttribPointerARB / glEnableVertexAttribArrayARB
				break;
		}
	}

	for(iterator i = l_ClientStates.begin(); i != l_ClientStates.end(); i++){
		glEnableClientState(*i);
	}

	// render using index buffer
	index_buffer_opengl *l_IndexBuffer = static_cast<index_buffer_opengl*>(
		a_Operation.m_IndexBuffer.get());

	glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, l_IndexBuffer->get_buffer_id());

	const GLvoid *l_IndexData = map_to_vbo_offset(0);

	GLenum l_IndexType = 
		a_Operation.m_IndexBuffer->get_index_buffer_format() == index_16bit
			? GL_UNSIGNED_SHORT
			: GL_UNSIGNED_INT;

	glDrawElements(
		get_draw_mode(a_Operation.m_DrawMode), 
		a_Operation.m_IndexBuffer->get_number_of_indices(), 
		l_IndexType, 
		l_IndexData
	);

	for(iterator i = l_ClientStates.begin(); i != l_ClientStates.end(); i++){
		glDisableClientState(*i);
	}

	glMatrixMode(GL_PROJECTION);
	glMatrixMode(GL_MODELVIEW);
}

krryn::core::refc_ptr<index_buffer> renderer_opengl::create_index_buffer(size_t a_NumberOfIndices, index_buffer_format a_Format){
	return krryn::core::refc_ptr<index_buffer>(
		new index_buffer_opengl(a_NumberOfIndices, a_Format)
	);
}

krryn::core::refc_ptr<vertex_buffer> renderer_opengl::create_vertex_buffer(size_t a_VertexSizeInBytes, size_t a_NumberOfVertices){
	return krryn::core::refc_ptr<vertex_buffer>(
		new vertex_buffer_opengl(a_VertexSizeInBytes, a_NumberOfVertices)
	);
}

const GLvoid *renderer_opengl::map_to_vbo_offset(size_t a_OffsetInBytes){
	return ((char *)0 + a_OffsetInBytes);
}

GLenum renderer_opengl::get_draw_mode(draw_mode a_Mode){
	switch(a_Mode){
		case mode_point_list:
			return GL_POINTS;
		case mode_line_list:
			return GL_LINES;
		case mode_line_strip:
			return GL_LINE_STRIP;
		case mode_triangle_list:
			return GL_TRIANGLES;
		case mode_triangle_strip:
			return GL_TRIANGLE_STRIP;
		case mode_triangle_fan:
			return GL_TRIANGLE_FAN;
	}

	throw std::runtime_error("Requesting invalid draw mode");
}

GLint renderer_opengl::get_element_size(element_type a_Element){
	switch(a_Element){
		case type_float1:
		case type_short1:
		case type_argb:
			return 1;

		case type_float2:
		case type_short2:
			return 2;

		case type_float3:
		case type_short3:
			return 3;

		case type_float4:
		case type_short4:
		case type_unsigned_byte4:
			return 4;
	}

	throw std::runtime_error("Requesting element size of invalid element " + (int)a_Element);
}

GLenum renderer_opengl::get_element_type(element_type a_Element){
	switch(a_Element){
		case type_float1:
		case type_float2:
		case type_float3:
		case type_float4:
			return GL_FLOAT;
		case type_short1:
		case type_short2:
		case type_short3:
		case type_short4:
			return GL_SHORT;
		case type_argb:
		case type_unsigned_byte4:
			return GL_UNSIGNED_BYTE;
	}

	throw std::runtime_error("Requesting element type of invalid element " + (int)a_Element);
}